﻿/*
 * Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh)
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
#ifndef UTIL_MINI_H
#define UTIL_MINI_H

#include <map>
#include <string>
#include <vector>
#include <fstream>
#include <cstring>
#include <cassert>
#include <iostream>
#include <exception>
#include "Util/util.h"
using namespace std;

namespace toolkit {


template<typename key, typename variant>
class mINI_basic: public map<key, variant> {
    // Public API : existing map<> interface plus following methods
public:
    void parse(const string &text) {
        // reset, split lines and parse
        vector<string> lines = tokenize(text, "\n");
        string symbol, tag;
        for (auto &line : lines){
            // trim blanks
            line = trim(line);
            // split line into tokens and parse tokens
            if(line.empty() || line.front() == ';' || line.front() == '#'){
                continue;
            }
            if (line.size() >= 3 && line.front() == '[' && line.back() == ']') {
                tag = trim(line.substr(1, line.size() - 2));
            } else {
                auto at = line.find('=');
                symbol = trim(tag + "." + line.substr(0, at));
                (*this)[symbol] = (at == string::npos ? string() : trim(line.substr(at + 1)));
            }
        }
    }
    void parseFile(const string &fileName = exePath() + ".ini") {
        ifstream in(fileName, ios::in | ios::binary | ios::ate);
        if (!in.good()) {
            stringstream ss;
            ss << "invalid ini file:" << fileName;
            throw invalid_argument(ss.str());
        }
        long size = in.tellg();
        in.seekg(0, ios::beg);
        string buf;
        buf.resize(size);
        in.read((char *) buf.data(), size);
        parse(buf);
    }

    string dump(const string &header = "; auto-generated by mINI class {",
            const string &footer = "; } ---") const {
        string output(header + (header.empty() ? "" : "\r\n")), tag;
        for (auto &pr : *this) {
            vector<string> kv = tokenize(pr.first, ".");
            if (tag != kv[0]) {
                output += "\r\n[" + (tag = kv[0]) + "]\r\n";
            }
            output += kv[1] + "=" + pr.second + "\r\n";
        }
        return output + "\r\n" + footer + (footer.empty() ? "" : "\r\n");
    }

    void dumpFile(const string &fileName = exePath() + ".ini") {
        ofstream out(fileName, ios::out | ios::binary | ios::trunc);
        auto dmp = dump();
        out.write(dmp.data(),dmp.size());
    }

    static mINI_basic &Instance();

private:
    vector<string> tokenize(const string &self, const string &chars) const {
        vector<string> tokens(1);
        string map(256, '\0');
        for (const unsigned char &ch : chars) {
            map[ch] = '\1';
        }
        for (const unsigned char &ch : self) {
            if (!map.at(ch)) {
                tokens.back().push_back(char(ch));
            } else if (tokens.back().size()) {
                tokens.push_back(string());
            }
        }
        while (tokens.size() && tokens.back().empty()) {
            tokens.pop_back();
        }
        return tokens;
    }
};

//  handy variant class as key/values
struct variant: public string {
    template<typename T>
    variant(const T &t) :
    string(to_string(t)) {
    }
    template<size_t N>
    variant(const char (&s)[N]) :
    string(s, N) {
    }
    variant(const char *cstr) :
        string(cstr) {
    }
    variant(const string &other = string()) :
        string(other) {
    }
    template<typename T>
    operator T() const {
        T t;
        stringstream ss;
        return ss << *this && ss >> t ? t : T();
    }
    template<typename T> bool operator ==(const T &t) const {
        return 0 == this->compare(variant(t));
    }
    bool operator ==(const char *t) const {
        return this->compare(t) == 0;
    }
    template<typename T>
    T as() const {
        return (T) (*this);
    }
};

using mINI = mINI_basic<string, variant>;

}  // namespace toolkit
#endif //UTIL_MINI_H

